package com.ikingtech.platform.service.system.organization.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.data.helper.DragHelper;
import com.ikingtech.framework.sdk.data.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.enums.system.organization.OrganizationStatusEnum;
import com.ikingtech.framework.sdk.enums.system.organization.OrganizationStatusEnums;
import com.ikingtech.framework.sdk.label.api.LabelApi;
import com.ikingtech.framework.sdk.label.model.LabelAssignDTO;
import com.ikingtech.framework.sdk.organization.api.OrgDeptApi;
import com.ikingtech.framework.sdk.organization.api.OrgUserApi;
import com.ikingtech.framework.sdk.organization.api.OrganizationApi;
import com.ikingtech.framework.sdk.organization.model.OrganizationDTO;
import com.ikingtech.framework.sdk.organization.model.OrganizationQueryParamDTO;
import com.ikingtech.framework.sdk.organization.model.OrganizationUserDTO;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.organization.entity.OrganizationDO;
import com.ikingtech.platform.service.system.organization.exception.OrganizationExceptionInfo;
import com.ikingtech.platform.service.system.organization.service.ModelConverter;
import com.ikingtech.platform.service.system.organization.service.OrganizationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.ikingtech.framework.sdk.context.constant.CommonConstants.ROOT_ORG_ID;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/organization", name = "系统管理-组织管理", description = "系统管理-组织管理，不支持多个根组织，所有组织均归属于默认根组织，默认跟组织编号为root")
public class OrganizationController implements OrganizationApi {

    private final OrganizationService service;

    private final OrgDeptApi orgDeptApi;

    private final OrgUserApi orgUserApi;

    private final LabelApi labelApi;

    private final ModelConverter converter;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<String> add(OrganizationDTO organization) {
        if (Boolean.TRUE.equals(this.service.nameExist(organization.getName()))) {
            throw new FrameworkException(OrganizationExceptionInfo.DUPLICATE_ORG_NAME);
        }
        OrganizationDO entity = Tools.Bean.copy(organization, OrganizationDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        entity.setSortOrder(this.service.getMaxSortOrder(entity.getParentId()) + 1);
        entity.setTenantCode(Me.tenantCode());
        this.assignLabel(entity.getId(), organization.getLabelIds());
        this.service.save(entity);
        return R.ok(entity.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        OrganizationDO entity = this.service.getById(id);
        if (null == entity) {
            return R.ok();
        }
        if (Boolean.TRUE.equals(entity.getDefaultFlag())) {
            throw new FrameworkException(OrganizationExceptionInfo.DEFAULT_ORG_NOT_DELETE);
        }
        List<String> ids = this.service.listIdByFullPath(entity.getFullPath());
        if (Tools.Coll.isBlank(ids)) {
            return R.ok();
        }
        this.orgDeptApi.moveDepartment(entity.getParentId(), ids);
        this.service.removeBatchByIds(ids);
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<Object> update(OrganizationDTO organization) {
        if (!Boolean.TRUE.equals(this.service.exist(organization.getId()))) {
            throw new FrameworkException(OrganizationExceptionInfo.ORG_NOT_FOUND);
        }
        OrganizationDO entity = Tools.Bean.copy(organization, OrganizationDO.class);
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        entity.setTenantCode(Me.tenantCode());
        this.service.updateById(entity);
        this.assignLabel(entity.getId(), organization.getLabelIds());
        return R.ok();
    }

    @Override
    public R<List<OrganizationDTO>> page(OrganizationQueryParamDTO queryParam) {
        return R.ok(this.service.listPage(queryParam, Me.tenantCode()).convertBatch(this.converter::modelConvert));
    }

    @Override
    public R<List<OrganizationDTO>> all() {
        return R.ok(Tools.Coll.convertList(this.service.listByTenantCode(Me.tenantCode()), entity -> Tools.Bean.copy(entity, OrganizationDTO.class)));
    }

    @Override
    public R<OrganizationDTO> detail(String id) {
        if (Tools.Str.isBlank(id)) {
            return R.ok();
        }
        OrganizationDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(OrganizationExceptionInfo.ORG_NOT_FOUND);
        }
        return R.ok(Tools.Bean.copy(entity, OrganizationDTO.class));
    }

    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        DragHelper<OrganizationDO> dragHelper = DragHelperBuilder.builder(
                        () -> this.service.gt(dragParam.getParentId(), dragParam.getCurrentOrder()),
                        () -> this.service.gt(dragParam.getTargetParentId(), dragParam.getTargetOrder()),
                        (starOrder, endOrder) -> this.service.between(dragParam.getParentId(), starOrder, endOrder)
                )
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                .currentNode(() -> this.service.getById(dragParam.getCurrentId()))
                .targetNode(() -> this.service.getById(dragParam.getTargetId()))
                .maxSortOrder(() -> this.service.getMaxSortOrder(dragParam.getTargetId()))
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        this.service.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    @Override
    public R<Object> disable(String id) {
        OrganizationDO organizationDO = new OrganizationDO();
        //停用状态
        organizationDO.setStatus(OrganizationStatusEnums.DEACTIVE.name());
        this.service.update(organizationDO, Wrappers.<OrganizationDO>update().eq("id", id).or().eq("parent_id", id));
        return R.ok();
    }

    @Override
    public R<Object> enable(String id) {
        OrganizationDO organizationDO = new OrganizationDO();
        organizationDO.setId(id);
        //停用状态
        organizationDO.setStatus(OrganizationStatusEnums.ACTIVE.name());
        this.service.updateById(organizationDO);
        return R.ok();
    }

    @Override
    public R<List<OrganizationUserDTO>> pageUserById(OrganizationQueryParamDTO queryParam) {
        List<String> deptIds = this.orgDeptApi.loadDepartmentIds(queryParam.getId());
        return R.ok(this.orgUserApi.loadUserByDeptIds(queryParam.getPage(), queryParam.getRows(), deptIds));
    }

    @Override
    public R<List<OrganizationDTO>> listSubAll(OrganizationQueryParamDTO queryParam) {
        List<String> dataScope = Me.dataScope();
        if (Me.invalidDataScope(dataScope)) {
            return R.ok(new ArrayList<>());
        }
        List<String> orgIds = this.orgDeptApi.loadOrganizationIdByDepartmentIds(dataScope);
        List<OrganizationDO> allOrgEntities = this.service.listByCondition(queryParam, Me.tenantCode());
        Map<String, OrganizationDO> allOrgMap = Tools.Coll.convertMap(allOrgEntities, OrganizationDO::getId);
        List<OrganizationDTO> result = new ArrayList<>();
        allOrgMap.forEach((id, entity) -> {
            if (orgIds.contains(id)) {
                OrganizationDTO org = Tools.Bean.copy(entity, OrganizationDTO.class);
                org.setInDataScope(true);
                result.add(org);
                List<String> parentIds = Tools.Str.split(entity.getFullPath());
                Tools.Coll.filter(parentIds, parentId -> !parentId.equals(entity.getId())).forEach(parentId -> {
                    if (!Tools.Coll.contains(result, OrganizationDTO::getId, parentId)) {
                        OrganizationDTO parentOrg = Tools.Bean.copy(allOrgMap.get(parentId), OrganizationDTO.class);
                        parentOrg.setInDataScope(orgIds.contains(parentId));
                        result.add(parentOrg);
                    }
                });
            }
        });
        return R.ok(result);
    }

    @Override
    public R<List<OrganizationDTO>> listSubByParentId(String parentId) {
        return R.ok(Tools.Coll.convertList(this.service.listByParentId(parentId), entity -> Tools.Bean.copy(entity, OrganizationDTO.class)));
    }

    @Override
    public R<List<OrganizationDTO>> listByLoginUser() {
        List<String> orgIds;
        if (Me.isAdmin()) {
            orgIds = this.service.listIdByTenantCode(Me.tenantCode());
        } else {
            List<String> deptIds = this.orgUserApi.loadDeptIdByUserId(Me.id(), Me.tenantCode());
            if (Tools.Coll.isBlank(deptIds)) {
                return R.ok(new ArrayList<>());
            }
            orgIds = this.orgDeptApi.loadOrganizationIdByDepartmentIds(deptIds);
        }
        if (Tools.Coll.isBlank(orgIds)) {
            return R.ok(new ArrayList<>());
        }

        return R.ok(Tools.Coll.convertList(this.service.listByIds(orgIds), entity -> Tools.Bean.copy(entity, OrganizationDTO.class)));
    }

    @EventListener
    public void initTenantOrgEventListener(TenantInitEvent event) {
        OrganizationDO entity = new OrganizationDO();
        entity.setId(Tools.Str.format("{}_{}", event.getCode(), ROOT_ORG_ID));
        entity.setName(event.getName());
        entity.setDefaultFlag(Boolean.TRUE);
        entity.setParentId(Tools.Str.EMPTY);
        entity.setTenantCode(event.getCode());
        entity.setGrade(1);
        entity.setStatus(OrganizationStatusEnum.ENABLE.name());
        entity.setFullPath(entity.getId());
        this.service.save(entity);
    }

    @EventListener
    public void deleteTenantOrgEventListener(TenantDeleteEvent event) {
        this.service.removeByTenantCode(event.getCode());
    }

    private void assignLabel(String orgId, List<String> labelIds) {
        LabelAssignDTO labelAssign = new LabelAssignDTO();
        labelAssign.setBusinessId(orgId);
        labelAssign.setLabelIds(labelIds);
        this.labelApi.assign(labelAssign);
    }
}
